/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package tuplestoreserver;

import core.Callback;
import core.IServerCallBack;
import core.ProtocolSD;
import core.ProtocolSD.Operation;
import core.Result;
import core.ServerCallBack;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import logging.LOG;
import protocol.TSFile;
import protocol.TSFileTag;

/**
 *
 * @author Jorge
 */
public class ClientListener implements ProtocolSD.IProtocolListener {

    /** Operação corrente*/
    private Operation operation;
    /** lista de chaves a tratar*/
    private List<Integer> currentKeys;
    /** flag de verificação de pedido*/
    private boolean close = false;
    /** lista temporária para ler inteiro (evento parcial)*/
    private List<byte[]> tmp = new LinkedList<byte[]>();
    /** tamanho do parametro (para evento parcial) */
    private int tmpParamSize = 0;
    /** número de parametros a tratar*/
    private int numArgs = 0;
    /** lista de chaves auxiliar*/
    private List<Integer> tmpKeys;
    /** key associada ao ultimo parametro lido*/
    private int key = -1;
    /** quantidade de bytes lidos (para evento parcial)*/
    private int tmpSize = 0;
    private boolean flag = false;
    private int ticket = 0;
    private Server server;
    private TSFile file;

    public ClientListener(Server server) {
        this.server = server;
    }

    public void onReadHeader(int operation, int numArgs, ProtocolSD protocolSD) {
        this.operation = Operation.values()[operation];
        LOG.append("Recebido pedido de operação " + this.operation.name());
        this.numArgs = numArgs;
        this.currentKeys = new ArrayList<Integer>(numArgs);

        if (this.operation == Operation.PUT_KEYS) {
            tmpKeys = new LinkedList<Integer>();
        }
    }

    public void onReadArgument(byte[] data, ProtocolSD protocolSD) {
        switch (operation) {
            case GET:
                onReadArgumentGet(data);
                break;
            case PUT_KEYS:
                onReadArgumentPutKets(data);
                break;
            case PUT_DATA:
                onReadArgumentPutData(data);
                break;
        }
    }

    public void onReadArgumentSize(int size, ProtocolSD protocolSD) {
        this.tmpParamSize = size;
        this.tmpSize = 0;
        tmp.clear();
        file = null;
        key = -1;
    }

    public void onReadAllArguments(ProtocolSD protocolSD) {
        protocolSD.getKey().interestOps(SelectionKey.OP_WRITE);
        switch (operation) {
            case GET:
                onReadAllArgumentsGet(protocolSD);
                break;
            case PUT_KEYS:
                onReadAllArgumentsPutKeys(protocolSD);
                break;
            case PUT_DATA:
                onReadAllArgumentsPutData(protocolSD);
                break;
        }
    }

    public void setCallBack(IServerCallBack callback) {
    }

    public IServerCallBack getCallBack() {
        return null;
    }

    public void onWriteComplete(ProtocolSD protocolSD) {
        protocolSD.getKey().interestOps(SelectionKey.OP_READ);
        switch (operation) {
            case GET:
                onWriteCompleteGet(protocolSD);
                break;
            case PUT_KEYS:
                onWriteCompletePutKeys(protocolSD);
                break;
            case PUT_DATA:
                onWriteCompletePutData(protocolSD);
                break;
        }
    }

    public void onAcceptConnect() {
    }

    public void onReadParcialParam(byte[] readedBytes, int tmpParcial, int remaning, ProtocolSD protocolSD) {
        switch (operation) {
            case PUT_DATA:
                onReadParcialParamPutData(readedBytes, tmpParcial, remaning, protocolSD);
                break;
        }
    }

    public boolean isClose() {
        return close;
    }

    private void removeKey(List<Integer> integers, int key) {
        Iterator<Integer> iterator = integers.iterator();
        while (iterator.hasNext()) {
            if (iterator.next().equals(key)) {
                iterator.remove();
                return;
            }
        }
    }

    public void onReadParcialParamPutData(byte[] readedBytes, int offset, int limit, final ProtocolSD protocolSD) {
        if (key == -1) {
            if (readedBytes.length >= 4 - tmpSize) {
                tmp.add(ProtocolSD.subArray(readedBytes, 0, 4 - tmpSize));
                byte[] bsAux = ProtocolSD.joinBuffer(tmp, 4);

                key = ProtocolSD.byteArrayToInt(bsAux, 0);
                file = server.getNewFileFromKey(key, ticket, tmpParamSize - 4);
                file.setCallback(new Callback() {

                    public void onError(Exception e) {
                    }

                    public void onResult(Result v) {
                        //commitKey(protocolSD, key);
                    }
                });
                //adiciona a chave á lista currente e remove da lista temporária
                currentKeys.add(key);
                removeKey(tmpKeys, key);
                if (4 - tmpSize != readedBytes.length) {
                    byte[] bs = ProtocolSD.subArray(readedBytes, 4 - tmpSize, readedBytes.length);
                    addToFile(key, bs, tmpParamSize - 4);
                }
                if (offset + limit == tmpParamSize) {
                    //Manda OK para cliente
                    commitKey(protocolSD, key);

                }
            } else {
                tmp.add(readedBytes);
                tmpSize += readedBytes.length;
            }
        } else {
            addToFile(key, readedBytes, tmpParamSize - 4);

            if (offset + limit == tmpParamSize) {
                commitKey(protocolSD, key);


                //fim de ficheiro
            }
        }
    }
    public static ProtocolSD protocolSDAUX;

    private void commitKey(final ProtocolSD protocolSD, final int key) {
        protocolSD.getKey().interestOps(0);
        protocolSDAUX = protocolSD;
        Callback<Integer, byte[]> callback = new Callback<Integer, byte[]>() {

            public void onError(Exception e) {
                throw new RuntimeException();
            }

            public void onResult(Result<Integer, byte[]> v) {
                //Manda OK para cliente
                byte b = v.getMap().get(key)[0];

                server.commitFileTmpKey(file, b == 1);


                protocolSD.writeInt(5);
                protocolSD.writeInt(key);
                protocolSD.write(b);
                flag = currentKeys.size() == numArgs;


                protocolSD.getKey().interestOps(SelectionKey.OP_WRITE);
                protocolSD.getKey().selector().wakeup();

            }
        };

        server.connect(Server.MASTER_IP, Server.MASTER_PORT, new ServerCallBack<Integer, byte[]>(callback) {

            public void onFinishClient(ProtocolSD protocolSD1) {
                // Faz comit de ficheiro
                protocolSD1.getKey().interestOps(SelectionKey.OP_WRITE);
                protocolSD1.write(new byte[]{(byte) ProtocolSD.Operation.COMMIT.ordinal(), 1});
                protocolSD1.writeInt(12);
                protocolSD1.writeInt(key);
                protocolSD1.writeInt(server.getServerKey());
                protocolSD1.writeInt(ticket);
            }

            @Override
            public void onWriteComplete(ProtocolSD protocolSD1) {
                //Espera por resposta
                protocolSD1.getKey().interestOps(SelectionKey.OP_READ);
            }

            @Override
            public void onReadArgument(byte[] data, ProtocolSD protocolSD) {
                ((ServerCallBack) this).onNewResult(ProtocolSD.byteArrayToInt(data, 0), ProtocolSD.subArray(data, 4, 5));
            }

            @Override
            public void onReadAllArguments(ProtocolSD protocolSD) {
                ((ServerCallBack) this).onFinish();
            }
        });
    }

    public void addToFile(int key, byte[] bs, int size) {
        server.drainSocketToFile(key, bs, size, file);
    }

    private void onWriteCompleteGet(ProtocolSD protocolSD) {
        if (currentKeys.size() == 0) {
            close = true;
            try {
                protocolSD.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }

        } else {
            protocolSD.getKey().interestOps(SelectionKey.OP_WRITE);
            Integer key = currentKeys.remove(0);
            TSFile fileTag = server.getReadFile(key);
            protocolSD.writeInt(4 + fileTag.getFileTag().getFileSize());
            protocolSD.writeInt(key);
            server.drainFileToSocket(protocolSD, fileTag, new Callback<Integer, Boolean>() {

                public void onError(Exception e) {
                }

                public void onResult(Result<Integer, Boolean> v) {
                }
            });
        }
    }

    private void onWriteCompletePutKeys(ProtocolSD protocolSD) {
        if (flag) {
            protocolSD.getKey().interestOps(SelectionKey.OP_READ);
            flag = false;
        }

    }

    private void onWriteCompletePutData(ProtocolSD protocolSD) {
        if (currentKeys.size() == numArgs && flag) {
            close = true;
            flag = false;
            try {
                protocolSD.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    public void onReadArgumentPutKets(byte[] data) {
        tmpKeys.add(ProtocolSD.byteArrayToInt(data, 0));
    }

    public void onReadArgumentGet(byte[] data) {
        currentKeys.add(ProtocolSD.byteArrayToInt(data, 0));
    }

    private void onReadArgumentPutData(byte[] data) {
        // É executado o evento parcial
    }

    public void onReadAllArgumentsGet(ProtocolSD protocolSD) {
        protocolSD.write(new byte[]{(byte) ProtocolSD.Operation.DATA.ordinal(), (byte) currentKeys.size()});


        protocolSD.getKey().interestOps(SelectionKey.OP_WRITE);
        Integer key = currentKeys.remove(0);
        TSFile fileTag = server.getReadFile(key);
        if (fileTag == null) {
            protocolSD.writeInt(4);
            protocolSD.writeInt(key);
        } else {
            protocolSD.writeInt(4 + fileTag.getFileTag().getFileSize());
            protocolSD.writeInt(key);
            server.drainFileToSocket(protocolSD, fileTag, new Callback<Integer, Boolean>() {

                public void onError(Exception e) {
                }

                public void onResult(Result<Integer, Boolean> v) {
                }
            });
        }
    }

    public void onReadAllArgumentsPutKeys(final ProtocolSD protocolSD) {
        Callback<Integer, Integer> callback = new Callback<Integer, Integer>() {

            public void onError(Exception e) {
                throw new RuntimeException();
            }

            public void onResult(Result<Integer, Integer> v) {
                for (Entry<Integer, Integer> entry : v.getMap().entrySet()) {
                    if (entry.getValue() == 0) {
                        removeKey(tmpKeys, entry.getKey());
                    } else {
                        ClientListener.this.ticket = entry.getValue();
                    }
                }
                protocolSD.write(new byte[]{(byte) ProtocolSD.Operation.DONE.ordinal(), (byte) tmpKeys.size()});
                protocolSD.getKey().interestOps(SelectionKey.OP_WRITE);
                protocolSD.getKey().selector().wakeup();
                flag = true;
            }
        };

        server.connect(Server.MASTER_IP, Server.MASTER_PORT, new ServerCallBack<Integer, Integer>(callback) {

            public void onFinishClient(ProtocolSD protocolSD1) {
                // Pede Ticket ao Master
                protocolSD1.getKey().interestOps(SelectionKey.OP_WRITE);
                tmpKeys.add(0, server.getServerKey());
                protocolSD1.write((byte) ProtocolSD.Operation.TICKET.ordinal(), tmpKeys);
                tmpKeys.remove(0);
            }

            @Override
            public void onWriteComplete(ProtocolSD protocolSD1) {
                //Espera por resposta
                
                    protocolSD1.getKey().interestOps(SelectionKey.OP_READ);
                
            }

            @Override
            public void onReadArgument(byte[] data, ProtocolSD protocolSD) {
                ((ServerCallBack) this).onNewResult(ProtocolSD.byteArrayToInt(data, 0), ProtocolSD.byteArrayToInt(data, 4));
            }

            @Override
            public void onReadAllArguments(ProtocolSD protocolSD) {
                ((ServerCallBack) this).onFinish();
            }
        });

    }

    public void onReadAllArgumentsPutData(ProtocolSD protocolSD) {
    }
}
